/*
 * File:    ClientContext.java
 * Created: 23-Oct-2005
 * Version: $Id: //depot/bitgate/bsse/src/bitgate/util/service/ClientContext.java#1 $
 *
 * COPYRIGHT (C) 2005, Bitgate Software, LLC.  All Rights Reserved.
 *
 * $Log$
 *
 * software@bitgatesoftware.com
 */

package com.bitgate.util.service.client;

import static com.bitgate.util.debug.Debug.*;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;

import com.bitgate.util.service.acl.ACL;
import com.bitgate.util.socket.ClientChannel;

/**
 * This class acts as a container for a client connected to a <code>Service</code>.  It contains the <code>ClientChannel</code>
 * object, and any pertinent information regarding the client - such as IP address, connection time, etc.
 * 
 * @author Kenji Hollis &lt;kenji@bitgatesoftware.com&gt;
 * @version $Id: //depot/bitgate/bsse/src/bitgate/util/service/ClientContext.java#1 $
 */
public class ClientContext
{
    private final WeakReference<ClientChannel> cChannel;
    private final long clientId;
    private final ByteBuffer recvBuffer;
    
    /**
     * These are the different types of ACLs, identifying the permission of the underlying connected socket object.
     * 
     * @author kenji
     */
    public enum AclType {
    	/** Indicates a whitelisted connection. */
    	WHITE, 
    	
    	/** Indicates a neutrallisted connection. */
    	NEUTRAL,
    	
    	/** Indicates a blacklisted connection. */
    	BLACK
    }
    private final AclType aclType;
    
    private byte[] outputBuffer;

    /**
     * Constructor - creates a new <code>ClientContext</code> object.
     */
    public ClientContext(long clientId, ClientChannel cChannel)
    {
        this.cChannel = new WeakReference<ClientChannel>(cChannel);
        this.clientId = clientId;
        this.recvBuffer = ByteBuffer.allocate(8192);
        this.outputBuffer = null;
        
        if (ACL.getDefault().isNeutrallisted(cChannel.getSocketChannel().socket())) {
        	aclType = AclType.NEUTRAL;
        } else if (ACL.getDefault().isBlacklisted(cChannel.getSocketChannel().socket())) {
        	aclType = AclType.BLACK;
        } else if (ACL.getDefault().isWhitelisted(cChannel.getSocketChannel().socket())) {
        	aclType = AclType.WHITE;
        } else {
        	aclType = null;
        }

        if (isDebugEnabled()) {
        	debug("Client Context created: secure='" + cChannel.isSecure() + "' socket='" + cChannel.getSocketChannel() +
        			"' clientId='" + this.clientId + "' ACL='" + aclType + "'");
        }
    }
    
    /**
     * Returns the ACL type for the client, determined from constructor time.
     * 
     * @return AclType containing the type of access that was granted.
     */
    public AclType getAclType()
    {
    	return aclType;
    }

    /**
     * Returns the same as the creation time for this object.  This is how the Serial ID is created and treated.
     *
     * @return <code>long</code> containing the creation time.
     */
    public long getSerialId()
    {
    	return this.clientId;
    }

    /**
     * Returns the currently associated <code>ClientChannel</code> object.
     *
     * @return <code>ClientChannel</code> object.
     */
    public ClientChannel getClientChannel()
    {
        return this.cChannel.get();
    }
    
    /**
     * Returns the receive buffer for the client.
     * 
     * @return <ode>ByteBuffer</code> object.
     */
    public ByteBuffer getByteBuffer()
    {
    	return this.recvBuffer;
    }
    
    /**
     * Sets the output buffer to be sent to the connected <code>Socket</code> object.
     * 
     * @param outputData <code>byte[]</code> containing the data to send.
     */
    public void setOutputBuffer(byte[] outputData)
    {
    	outputBuffer = outputData;
    }
    
    /**
     * Sets the output buffer for the write phase of the client write.
     * 
     * @param outputData A <code>byte</code> array containing the output data.
     */
    public void appendOutputBuffer(byte[] outputData, int length)
    {
    	if (outputBuffer == null) {
    		outputBuffer = new byte[outputData.length];
    		System.arraycopy(outputData, 0, outputBuffer, 0, length);
    	} else {
    		byte[] newOutputBuffer = new byte[outputBuffer.length + length];
    		
    		System.arraycopy(outputBuffer, 0, newOutputBuffer, 0, outputBuffer.length);
    		System.arraycopy(outputData, 0, newOutputBuffer, outputBuffer.length, length);
    		
    		outputBuffer = newOutputBuffer;
    		
    		// Garbage collect.
    		newOutputBuffer = null;
    	}
    }
    
    /**
     * Returns the byte array to be sent to the client.
     * 
     * @return <code>byte[]</code> containing the data to write.
     */
    public byte[] getOutputBuffer()
    {
    	return this.outputBuffer;
    }
    
    /**
     * Resets the state of the client object, clearing the output buffer and receive buffers.
     */
    public void reset()
    {
    	recvBuffer.clear();
    	outputBuffer = null;
    }
}
